package wrm.saferJava.aop.oval;

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;


import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

import wrm.saferJava.aop.GuardClassInjector;
import wrm.saferJava.aop.InjectorBinding;
import wrm.saferJava.oval.guard.Guarded;


/**
 * Aspect framework:
 * implements an interceptive call by redirecting all calls to all methods to a shadowing method:
 * Implementation:
 * renames all original methods and inserts new methods as originals which implement "around"-aspect
 * which is a call to OvalAopAdvice.intercept
 * 
 * 
 * @author Peter Mucha
 *
 */
public class OvalGuardInjector extends GuardClassInjector {

	public final static String METHOD_PROXY_QUALIFIER = "$$SF$$PROXY";
	public final static String ADVICE_PROXY_QUALIFIER = "$$SF$$PROXY$$ADVICE";
	

	public class MethodParameterStruct {
		public int access;
		public String name;
		public String descriptor;
		public String signature;
		public String[] throwings;

		public MethodParameterStruct(int access, String name, String descriptor,
				String signature, String[] throwings) {
			this.access = access;
			this.name = name;
			this.descriptor = descriptor;
			this.signature = signature;
			this.throwings = throwings;
		}
	}
	
	
	

	private List<MethodParameterStruct> proxyMethods = new LinkedList<MethodParameterStruct>();
	private MethodParameterStruct mParam;

	public OvalGuardInjector(ClassVisitor arg0, ClassLoader loader) {
		super(arg0, loader);
	}
	
	@Override
	public void visitSource(String source, String debug) {
		if (isGuardedClass())
			source = "<generated>";
		super.visitSource(source, debug);
	}
	
	@Override
	public void visit(int version, int access, String name, String signature,
			String superName, String[] interfaces) 
	{
		super.visit(version, access, name, signature, superName, interfaces);
		proxyMethods.clear();
	}
	
	@Override
	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		return super.visitAnnotation(desc, visible);
	}

	@Override
	public MethodVisitor visitMethod(int access, String name, String descriptor,
			String signature, String[] throwing) {
		if (((access & ACC_ABSTRACT) == 0) && isGuardedClass())
		{
			mParam = new MethodParameterStruct(access, name, descriptor, signature, throwing);
			if (!"<init>".equals(name))
			{
				proxyMethods.add(mParam);
				//mv = new AdviceInjector(mv, access, name, descriptor, curClass);
				name = name + METHOD_PROXY_QUALIFIER;
			}
			else//is a constructor? 
			{
				MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, throwing);
				return new ConstructorAdvice(mv,  mParam);
			
			}
		}
		
		return super.visitMethod(access, name, descriptor, signature, throwing);
	}
	
	
	private class ConstructorAdvice extends AdviceAdapter{

		private final MethodParameterStruct mParam;

		protected ConstructorAdvice(MethodVisitor mv, MethodParameterStruct mParam) {
			super(mv, mParam.access, mParam.name, mParam.descriptor);
			this.mParam = mParam;
			
		}
		
		@Override
		protected void onMethodEnter() {
			//boom? :D
			if (isGuardedClass())
				insertAdviceCall(mParam, mv, Type.getArgumentTypes(mParam.descriptor));
		}
	}

	
	
	@Override
	public void visitEnd() {
	
		if (isGuardedClass())
		{
			
			FieldVisitor fv = super.visitField(0, ADVICE_PROXY_QUALIFIER, "L"+Type.getInternalName(OvalAopAdvice.class)+";", null, null);
			fv.visitEnd();
			
			
			for(MethodParameterStruct mParam : proxyMethods)
				insert_proxyCall(mParam);
		}
		super.visitEnd();
	}
	
	
	private void insert_proxyCall(MethodParameterStruct mParam)
	{
		//Code:
		//create array of all args => args, same with classes
		//advice.intercept(this, methodName,classes, args)
		
		MethodVisitor mv = super.visitMethod(mParam.access, mParam.name, mParam.descriptor, mParam.signature, mParam.throwings);
		Type[] classes = Type.getArgumentTypes(mParam.descriptor);
		
		mv.visitCode();

		
		insertAdviceCall(mParam, mv, classes);
			
		
		mv.visitMaxs(0,0);
		mv.visitEnd();
	}

	private void insertAdviceCall(MethodParameterStruct mParam,
			MethodVisitor mv, Type[] classes) {
		int line = 0;
		mv.visitLineNumber(line++, new Label());
		mv.visitVarInsn(ALOAD, 0); //load this
		mv.visitLdcInsn(mParam.name);
		
		
		
		//type descriptor array
		mv.visitLineNumber(line++, new Label());
		mv.visitIntInsn(BIPUSH, classes.length);//array size
		mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
		
		int pos = 0;
		for(Type t : classes)
		{
			mv.visitInsn(DUP);
			mv.visitIntInsn(BIPUSH, pos);
			mv.visitLdcInsn(Type.getType(t.getDescriptor()));
			mv.visitInsn(AASTORE);
			pos++;
		}
		
		
		
		
		//create array of args
		mv.visitLineNumber(line++, new Label());
		mv.visitIntInsn(BIPUSH, classes.length);//array size
		mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
		
		pos = 0;
		for(Type t : classes)
		{
			mv.visitInsn(DUP);
			mv.visitIntInsn(BIPUSH, pos);
			mv.visitVarInsn(ALOAD, pos+1);
			mv.visitInsn(AASTORE);
			pos++;
		}
		
		//mv.visitVarInsn(ASTORE, la+1); //save array in locals

		mv.visitLineNumber(line++, new Label());
		
		Method intercept = null;
		try {
			intercept = OvalAopAdvice.class.getMethod("intercept", new Class[]{Object.class, String.class,Class[].class, Object[].class});
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			System.out.println("Cant fint interception method.");
		}
		
		mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(OvalAopAdvice.class), "intercept", Type.getMethodDescriptor(intercept));
		
		if (Type.getReturnType(mParam.descriptor).equals(Type.VOID_TYPE))
		{
			mv.visitInsn(POP); //pop return
			mv.visitInsn(RETURN);
		}
		else
		{
			mv.visitInsn(ARETURN);
		}
	}

	
}
